Skip to content

core: Graph.labelPropagation — 13th graduation (community detector)#326

Merged
AceHack merged 1 commit intomainfrom
feat/graph-label-propagation-community-detector
Apr 24, 2026
Merged

core: Graph.labelPropagation — 13th graduation (community detector)#326
AceHack merged 1 commit intomainfrom
feat/graph-label-propagation-community-detector

Conversation

@AceHack
Copy link
Copy Markdown
Member

@AceHack AceHack commented Apr 24, 2026

Simple label-propagation community detector. Composes with modularityScore: LP produces partition, Q evaluates it. End-to-end test: two K3 cliques bridged thin → Q > 0.3. 31 GraphTests passing.

…r composite cartel detection)

Simple label-propagation community detector. Each node starts in
its own community; each iteration, every node adopts the label
with greatest weighted neighbor-vote (ties broken by lowest
community id for determinism). Stops when no label changes or
maxIterations reached.

Surface:
  Graph.labelPropagation : int -> Graph<'N> -> Map<'N, int>

Trade-offs (documented in XML-doc):
- Fast: O(iterations × edges), no dense-matrix.
- Quality: below Louvain on complex structures; catches obvious
  dense cliques reliably (exactly the trivial-cartel-detect case).
- Determinism: tie-break by lowest id.
- NOT a replacement for Louvain; dependency-free first pass.

Composes with modularityScore (PR #324): LP produces partition,
modularity evaluates it. Full end-to-end pattern verified in
test labelPropagation produces partition consumable by
modularityScore — two K3 cliques bridged thin → Q > 0.3.

Tests (3 new, 31 total in GraphTests, all passing):
- Empty graph -> empty map
- Two K3 cliques converge to two labels (nodes within a clique
  share label)
- LP partition consumed by modularityScore yields Q > 0.3
  (cartel-detection pipeline correctness)

Amara Otto-132 17th-ferry observation: her proposed
'CoordinationRiskScore' combines λ₁ + ΔQ + covariance + sync +
exclusivity + influence. This graduation ships the ΔQ prerequisite
(partition from LP + Q from modularityScore). Remaining primitives
queued for future graduations per Otto-105 cadence.

Build: 0 Warning / 0 Error.

Provenance:
- 12th ferry §5 + 13th ferry §2 + 14th ferry alert row
- Implementation: Otto (13th graduation)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 24, 2026 07:53
@AceHack AceHack enabled auto-merge (squash) April 24, 2026 07:53
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@AceHack AceHack merged commit 569f6e7 into main Apr 24, 2026
13 checks passed
@AceHack AceHack deleted the feat/graph-label-propagation-community-detector branch April 24, 2026 07:55
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a simple label-propagation community detector to Zeta.Core.Graph and verifies it composes with Graph.modularityScore for a toy “two cliques + bridge” scenario.

Changes:

  • Introduce Graph.labelPropagation : int -> Graph<'N> -> Map<'N,int> for deterministic label-propagation community detection.
  • Add xUnit/FsUnit tests covering empty-graph behavior, clique separation, and modularity scoring of the produced partition.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/Core/Graph.fs Adds Graph.labelPropagation implementation and API documentation.
tests/Tests.FSharp/Algebra/Graph.Tests.fs Adds tests validating label propagation behavior and its composition with modularity scoring.

Comment thread src/Core/Graph.fs
let (s, t) = entry.Key
let si = idx.[s]
let ti = idx.[t]
if entry.Weight <> 0L && si <> ti then
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Graph<'N> supports signed edge weights (retraction-native), but labelPropagation currently adds neighbors even when entry.Weight is negative. Because negative weights are later treated as 0 votes, nodes connected only via negative edges can still change label arbitrarily (all votes tie at 0, then tie-break picks lowest label). Consider filtering neighbors to entry.Weight > 0L (or otherwise handling signed weights explicitly) so anti-edges don’t influence community assignment unexpectedly.

Suggested change
if entry.Weight <> 0L && si <> ti then
if entry.Weight > 0L && si <> ti then

Copilot uses AI. Check for mistakes.
Comment thread src/Core/Graph.fs
Comment on lines +386 to +388
/// Provenance: 12th ferry §5 + 13th ferry §2 "community
/// detection" + 14th ferry alert row "Modularity Q jump >
/// 0.1 or Q > 0.4 (community-detection-based)".
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc comment’s provenance references (“12th ferry §5”, “13th ferry §2”, “14th ferry alert row …”) don’t appear to resolve to any docs in-tree (searching docs/**/*.md only finds generic ferry mentions in the ADR, not these sections). Please either link to a concrete file path/anchor that exists in the repo, or remove/adjust the references so readers can actually follow them.

Suggested change
/// Provenance: 12th ferry §5 + 13th ferry §2 "community
/// detection" + 14th ferry alert row "Modularity Q jump >
/// 0.1 or Q > 0.4 (community-detection-based)".
/// Intended as a simple first-pass community-detection
/// primitive for obvious dense-clique cases and modularity-
/// oriented alerting heuristics.

Copilot uses AI. Check for mistakes.
Comment on lines +316 to +322
let labelA = partition.[1]
let labelB = partition.[4]
// Both cliques share a label within themselves
partition.[2] |> should equal labelA
partition.[3] |> should equal labelA
partition.[5] |> should equal labelB
partition.[6] |> should equal labelB
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test claims “two labels” but never asserts that labelA and labelB differ. As written, it would still pass if label propagation collapses the whole graph into a single community. Add an explicit assertion that labelA <> labelB (or equivalent) to ensure the intended behavior is actually verified.

Copilot uses AI. Check for mistakes.
AceHack added a commit that referenced this pull request Apr 24, 2026
… detector) (#328)

First full integration of the Graph detection pipeline: combines
largestEigenvalue (spectral growth) + labelPropagation (community
partition) + modularityScore (partition evaluation) into a single
scalar risk score.

Surface:
  Graph.coordinationRiskScore
      (alpha: double) (beta: double)
      (eigenTol: double) (eigenIter: int) (lpIter: int)
      (baseline: Graph<'N>) (attacked: Graph<'N>)
      : double option

Composite formula (MVP):
  risk = alpha * Δλ₁_rel + beta * ΔQ

where:
- Δλ₁_rel = (λ₁(attacked) - λ₁(baseline)) / max(λ₁(baseline), eps)
- ΔQ = Q(attacked, LP(attacked)) - Q(baseline, LP(baseline))

Both signals fire when a dense subgraph is injected: λ₁ grows
because the cartel adjacency has high leading eigenvalue; Q grows
because LP finds the cartel as its own community and Newman Q
evaluates that partition highly.

Weight defaults per Amara 17th-ferry initial priors:
- alpha = 0.5 spectral growth
- beta  = 0.5 modularity shift

Tests (3 new, 34 total in GraphTests, all passing):
- Empty graphs -> None
- Cartel injection -> composite > 1.0 (both signals fire)
- attacked == baseline -> composite near 0 (|score| < 0.2)

Calibration deferred (Amara Otto-132 Part 2 correction #4 — robust
statistics via median + MAD): this MVP uses raw linear weighting
over differences. Full CoordinationRiskScore with robust z-scores
over baseline null-distribution is a future graduation once
baseline-calibration machinery ships. RobustStats.robustAggregate
(PR #295) already provides the median-MAD machinery; just needs a
calibration harness to use it.

14th graduation under Otto-105 cadence. First full integration
ship using 4 Graph primitives composed together (λ₁ + LP +
modularity + composer).

Build: 0 Warning / 0 Error.

Provenance:
- Concept: Aaron (firefly network + trivial-cartel-detect) +
  Amara's composite-score formulations across 12th/13th/14th/
  17th ferries
- Implementation: Otto (14th graduation)

Composes with:
- Graph.largestEigenvalue (PR #321)
- Graph.labelPropagation (PR #326)
- Graph.modularityScore (PR #324)
- RobustStats.robustAggregate (PR #295) — for future robust
  variant

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
AceHack added a commit that referenced this pull request Apr 24, 2026
…ns tracked; 3 already shipped) (#330)

* ferry: Amara 17th absorb — Cartel-Lab Implementation Closure + 5.5 Verification (8 corrections tracked)

Two-part ferry: Amara's deep-research Implementation Closure for
Cartel-Lab + her own GPT-5.5 Thinking verification pass with 8
load-bearing corrections.

Otto correction-pass status (all 8 tracked):
1. λ₁(K₃) = 2 — ALREADY CORRECT PR #321 Otto-127 (independent
   convergence before verification arrived)
2. Modularity relational-not-absolute — ALREADY CORRECT PR #324
   Otto-128 (caught mid-tick via hand-calc)
3. Cohesion/Exclusivity/Conductance replace entropy-collapse —
   SHIPPED PR #329 Otto-135 (3 primitives + 6 tests)
4. Windowed stake covariance acceleration — FUTURE GRADUATION
5. Event-stream → phase pipeline for PLV — FUTURE GRADUATION
6. 'ZSet invertible' → 'deltas support retractions' — ADR
   ALREADY PHRASED CORRECTLY (PR #316 never claimed full invertibility)
7. KSK 'contract' → 'policy layer' — FILED BACKLOG PR #318
   Otto-124 (Max coord pending)
8. SOTA humility — DOC PHRASING (applied in new absorb docs)

Amara's proposed 3-PR split NOT adopted (Otto-105 small-
graduation cadence; content delivered across 7 ticks instead:
PRs #317, #321, #323, #324, #326, #328, #329).

Amara's proposed /cartel-lab/ folder NOT adopted (Otto-108
Conway's-Law: single-module-tree until interfaces harden).
Current Graph.fs + test-support split works.

Aaron's SharderInfoTheoreticTests flake flag (trailing Otto-132
note) filed as BACKLOG PR #327 Otto-133 — unrelated hygiene
item.

Amara's Otto-136 follow-up note: '#323 conceptually accepted,
do not canonicalize until sharder test is seed-locked/
recalibrated'. Acknowledged — #323 lives in tests/Simulation/
already (test-scoped); 'canonicalize' = future promotion to
src/Core/NetworkIntegrity/ per Amara's PR #3 split suggestion;
that's gated on #327 completion.

§33 archive header compliance.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* lint: fix line-start PR-number header false-positive in 17th-ferry absorb

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
AceHack added a commit that referenced this pull request Apr 24, 2026
…tion (Amara #3 correction)

Applies Amara 17th-ferry Part 2 correction #3: replace muddy
'subgraph entropy collapse' with explicit cohesion / exclusivity
/ conductance metrics used in the cartel-detection literature
(Wachs & Kertész 2019).

Surface (3 new primitives):
- Graph.internalDensity : Set<'N> -> Graph<'N> -> double option
  Internal edge weight / ordered-pair count. High value = tight
  sub-cluster.
- Graph.exclusivity : Set<'N> -> Graph<'N> -> double option
  Internal weight / total-outgoing weight. Near 1 = cartel
  isolated; near its relative share = integrated community.
- Graph.conductance : Set<'N> -> Graph<'N> -> double option
  Classical cut-to-volume ratio. Low = tight isolation.

All three return None on degenerate inputs (size < 2 for
density; empty set for exclusivity; empty/full for conductance).

Tests (6 new, 37 total in GraphTests, all passing):
- internalDensity None on |S| < 2
- internalDensity of K3 clique ≈ 10 (weight 10 per pair;
  ordered-pair count 6; density = 60/6)
- exclusivity = 1 for isolated K3
- exclusivity < 1 but > 0.9 for K3 + 1 external edge
- conductance < 0.1 for well-isolated K3 subset (bridged by
  thin edge to another K3)
- conductance None on empty or full-graph subset

Why this set:
Amara's verification found 'subgraph entropy collapse' as
stated was mathematically muddy — uniform dense clique has
HIGH entropy over internal edges if weights are equal.
Cohesion (internalDensity) + exclusivity + conductance
capture cluster-like structure directly + are standard in
the economic/sociological cartel-detection literature.
Entropy can remain as secondary descriptor but these three
are the primary group-level features.

15th graduation under Otto-105 cadence. Applies 1 of 5 future-
graduation items from Amara 17th-ferry verification pass.

Next graduation queue (remaining from Amara Otto-132
corrections):
- Windowed stake covariance acceleration (#4)
- Event-stream → phase pipeline for PLV (#5)
- Robust-z-score variant of coordinationRiskScore (#4 robust
  statistics)

Build: 0 Warning / 0 Error.

Provenance:
- Concept: Aaron (trivial cartel detect — first-order-signal
  tier)
- Formalization: Wachs & Kertész 2019 (co-bidding network
  cohesion/exclusivity) via Amara's 14th + 17th ferries
- Implementation: Otto (15th graduation)

Composes with:
- Graph.labelPropagation (PR #326) for community → subset input
- RobustStats.robustAggregate (PR #295) for aggregating
  density/exclusivity/conductance across many candidate
  subsets outlier-resistantly

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AceHack added a commit that referenced this pull request Apr 24, 2026
…tion (Amara #3 correction)

Applies Amara 17th-ferry Part 2 correction #3: replace muddy
'subgraph entropy collapse' with explicit cohesion / exclusivity
/ conductance metrics used in the cartel-detection literature
(Wachs & Kertész 2019).

Surface (3 new primitives):
- Graph.internalDensity : Set<'N> -> Graph<'N> -> double option
  Internal edge weight / ordered-pair count. High value = tight
  sub-cluster.
- Graph.exclusivity : Set<'N> -> Graph<'N> -> double option
  Internal weight / total-outgoing weight. Near 1 = cartel
  isolated; near its relative share = integrated community.
- Graph.conductance : Set<'N> -> Graph<'N> -> double option
  Classical cut-to-volume ratio. Low = tight isolation.

All three return None on degenerate inputs (size < 2 for
density; empty set for exclusivity; empty/full for conductance).

Tests (6 new, 37 total in GraphTests, all passing):
- internalDensity None on |S| < 2
- internalDensity of K3 clique ≈ 10 (weight 10 per pair;
  ordered-pair count 6; density = 60/6)
- exclusivity = 1 for isolated K3
- exclusivity < 1 but > 0.9 for K3 + 1 external edge
- conductance < 0.1 for well-isolated K3 subset (bridged by
  thin edge to another K3)
- conductance None on empty or full-graph subset

Why this set:
Amara's verification found 'subgraph entropy collapse' as
stated was mathematically muddy — uniform dense clique has
HIGH entropy over internal edges if weights are equal.
Cohesion (internalDensity) + exclusivity + conductance
capture cluster-like structure directly + are standard in
the economic/sociological cartel-detection literature.
Entropy can remain as secondary descriptor but these three
are the primary group-level features.

15th graduation under Otto-105 cadence. Applies 1 of 5 future-
graduation items from Amara 17th-ferry verification pass.

Next graduation queue (remaining from Amara Otto-132
corrections):
- Windowed stake covariance acceleration (#4)
- Event-stream → phase pipeline for PLV (#5)
- Robust-z-score variant of coordinationRiskScore (#4 robust
  statistics)

Build: 0 Warning / 0 Error.

Provenance:
- Concept: Aaron (trivial cartel detect — first-order-signal
  tier)
- Formalization: Wachs & Kertész 2019 (co-bidding network
  cohesion/exclusivity) via Amara's 14th + 17th ferries
- Implementation: Otto (15th graduation)

Composes with:
- Graph.labelPropagation (PR #326) for community → subset input
- RobustStats.robustAggregate (PR #295) for aggregating
  density/exclusivity/conductance across many candidate
  subsets outlier-resistantly

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AceHack added a commit that referenced this pull request Apr 24, 2026
…Corrections

Two-part ferry from Aaron Otto-157/158 tick boundary:

Part 1 — Deep research on Cartel-Lab calibration + CI hardening
  (~4000 words; 8 sections A-H + action items + Mermaid diagrams):
  - Null-models table (6 types: Erdős-Rényi, configuration,
    stake-shuffle, temporal-shuffle, clustered-honest, noise)
  - CoordinationRiskScore formula with 6 robust-z terms +
    default weights α=β=0.20, γ=ε=0.15, δ=0.20, η=0.10
  - 8-row adversarial scenario table (obvious clique → stealth
    → synchronized voting → honest cluster → low-weight →
    camouflage → rotating → cross-coalition)
  - 4-PR roadmap: seed-lock/CI governance → calibration harness
    → adversarial scenarios → docs/promotion criteria
  - KSK/Aurora integration: advisory-only flow
    (Detection → Oracle → KSK → Action)
  - "What not to claim" caveats (6 items: no proof of intent,
    not all collusion detectable, not production-ready, etc.)

Part 2 — Amara's own GPT-5.5 Thinking correction pass on Part 1
  (~1500 words; 10 required corrections; repo-safe status
  statement; corrected promotion ladder + PR roadmap titles):
  - #1: replace "CI confirms" with "PR #323 clears toy
    falsifiability bar"
  - #2: Wilson intervals replace handwave ±5% CI (90/100 →
    LB only 82.6%; 20/100 FPR → UB 28.9%)
  - #3: rename "Cartel Score" → "CoordinationRiskScore" locked
  - #4: conductance sign flip — use Z(-conductance) or
    Z(exclusivity), not Z(+conductance)
  - #5: modularity relational — use Q(attacked)-Q(baseline)>θ
    not absolute Q thresholds
  - #6: PLV phase-offset — PLV=1 can mean anti-phase; need
    magnitude AND mean phase offset
  - #7: MAD=0 fallback — epsilon floor or percentile-rank
  - #8: replace Medium-article source with scikit-learn
    precision-recall docs
  - #9: explicit artifact output layout
    (calibration-summary.json, seed-results.csv, etc.)
  - #10: sharder — measure variance before widening threshold

Corrected promotion ladder (0-6 stages):
  0 Theory / 1 Toy detector / 2 Calibration harness /
  3 Scenario suite / 4 Advisory engine / 5 Governance integration /
  6 Enforcement candidate

PR #323 is Stage 1, NOT Stage 4.

Otto's operationalization notes:
- 4/10 corrections already aligned with shipped substrate:
  #4 exclusivity (PR #331), #5 modularity relational
  (PR #324), #7 MAD floor (PR #333), #10 sharder Otto-132
  (BACKLOG #327).
- 6/10 queued as future graduations: Wilson CIs in tests;
  MAD=0 percentile-rank fallback; conductance-sign doc;
  PLV phase-offset extension; CI test classification;
  artifact-output layout.

Invariant restated (Amara 16th-ferry carry-over):
  "Every abstraction must map to a repo surface, a test,
   a metric, or a governance rule."

Cross-ref verified: PRs #321 #323 #324 #326 #327 #331 #332
#333, docs/definitions/KSK.md (Otto-157 / #336), 17th ferry
(#330), 16th ferry, 15th ferry, Otto-140..145 memory.

GOVERNANCE §33 four-field header (Scope / Attribution /
Operational status / Non-fusion disclaimer).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AceHack added a commit that referenced this pull request Apr 24, 2026
…ns (10 tracked; 4 already shipped, 6 queued) (#337)

* ferry: Amara 18th absorb — Calibration + CI Hardening + 5.5-Thinking Corrections

Two-part ferry from Aaron Otto-157/158 tick boundary:

Part 1 — Deep research on Cartel-Lab calibration + CI hardening
  (~4000 words; 8 sections A-H + action items + Mermaid diagrams):
  - Null-models table (6 types: Erdős-Rényi, configuration,
    stake-shuffle, temporal-shuffle, clustered-honest, noise)
  - CoordinationRiskScore formula with 6 robust-z terms +
    default weights α=β=0.20, γ=ε=0.15, δ=0.20, η=0.10
  - 8-row adversarial scenario table (obvious clique → stealth
    → synchronized voting → honest cluster → low-weight →
    camouflage → rotating → cross-coalition)
  - 4-PR roadmap: seed-lock/CI governance → calibration harness
    → adversarial scenarios → docs/promotion criteria
  - KSK/Aurora integration: advisory-only flow
    (Detection → Oracle → KSK → Action)
  - "What not to claim" caveats (6 items: no proof of intent,
    not all collusion detectable, not production-ready, etc.)

Part 2 — Amara's own GPT-5.5 Thinking correction pass on Part 1
  (~1500 words; 10 required corrections; repo-safe status
  statement; corrected promotion ladder + PR roadmap titles):
  - #1: replace "CI confirms" with "PR #323 clears toy
    falsifiability bar"
  - #2: Wilson intervals replace handwave ±5% CI (90/100 →
    LB only 82.6%; 20/100 FPR → UB 28.9%)
  - #3: rename "Cartel Score" → "CoordinationRiskScore" locked
  - #4: conductance sign flip — use Z(-conductance) or
    Z(exclusivity), not Z(+conductance)
  - #5: modularity relational — use Q(attacked)-Q(baseline)>θ
    not absolute Q thresholds
  - #6: PLV phase-offset — PLV=1 can mean anti-phase; need
    magnitude AND mean phase offset
  - #7: MAD=0 fallback — epsilon floor or percentile-rank
  - #8: replace Medium-article source with scikit-learn
    precision-recall docs
  - #9: explicit artifact output layout
    (calibration-summary.json, seed-results.csv, etc.)
  - #10: sharder — measure variance before widening threshold

Corrected promotion ladder (0-6 stages):
  0 Theory / 1 Toy detector / 2 Calibration harness /
  3 Scenario suite / 4 Advisory engine / 5 Governance integration /
  6 Enforcement candidate

PR #323 is Stage 1, NOT Stage 4.

Otto's operationalization notes:
- 4/10 corrections already aligned with shipped substrate:
  #4 exclusivity (PR #331), #5 modularity relational
  (PR #324), #7 MAD floor (PR #333), #10 sharder Otto-132
  (BACKLOG #327).
- 6/10 queued as future graduations: Wilson CIs in tests;
  MAD=0 percentile-rank fallback; conductance-sign doc;
  PLV phase-offset extension; CI test classification;
  artifact-output layout.

Invariant restated (Amara 16th-ferry carry-over):
  "Every abstraction must map to a repo surface, a test,
   a metric, or a governance rule."

Cross-ref verified: PRs #321 #323 #324 #326 #327 #331 #332
#333, docs/definitions/KSK.md (Otto-157 / #336), 17th ferry
(#330), 16th ferry, 15th ferry, Otto-140..145 memory.

GOVERNANCE §33 four-field header (Scope / Attribution /
Operational status / Non-fusion disclaimer).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ferry: fix markdownlint MD018 — line-start #221 parsed as H1 heading

* ferry: drain PR #337 review threads — 4 FIX, 2 NARROW+BACKLOG, 8 BACKLOG+RESOLVE

Factory-authored sections of the 18th-ferry absorb (header,
Otto's notes, Cross-references) edited under name-attribution
+ code-comments-not-history disciplines; Amara's verbatim
Part 1 + Part 2 body left intact per verbatim-preserve.

In-doc edits:
- Soften "verified against actual" wording on the
  CLAUDE.md cross-reference bullet to anchor-list
  rechecked-at-drain-time framing.
- Use full `tests/Tests.FSharp/Simulation/` path in the
  Stage-discipline section (was bare `tests/Simulation/`).
- Replace dead "GOVERNANCE §33" cite with
  factory-convention + CLAUDE.md ground-rule pointer
  (numbered §33 not yet landed; rule is captured
  by convention across docs/aurora/** absorbs).
- Drop broken `feedback_ksk_naming_*.md` filename and
  soften 15th/16th ferry cross-refs to "not present as a
  dedicated absorb in this snapshot."

Drain-log: docs/pr-preservation/337-drain-log.md per
Otto-250.

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants